<!doctype html>
<html>
    <head>
        <title>HtmlNode 手册 - HT for Web</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="user-scalable=yes, width=1024">
        <style type="text/css">
            h1, h2, h3, h4, h5, h6, p, blockquote {
                margin: 0;
                padding: 0;
            }
            body {
                font-family: "Helvetica Neue", Helvetica, "Hiragino Sans GB", Arial, sans-serif;
                font-size: 13px;
                line-height: 18px;
                color: #737373;
                background-color: white;
                margin: 10px 13px 10px 13px;
            }
            table {
                margin: 10px 0 15px 0;
                border-collapse: collapse;
            }
            td,th {
                border: 1px solid #ddd;
                padding: 3px 10px;
            }
            th {
                padding: 5px 10px;
            }
            a {
                color: #0069d6;
            }
            a:hover {
                color: #0050a3;
                text-decoration: none;
            }
            a img {
                border: none;
            }
            p {
                margin-bottom: 9px;
            }
            h1, h2, h3, h4, h5, h6 {
                color: #404040;
                line-height: 36px;
            }
            h1 {
                margin-bottom: 18px;
                font-size: 30px;
            }
            h2 {
                font-size: 24px;
            }
            h3 {
                font-size: 18px;
            }
            h4 {
                font-size: 16px;
            }
            h5 {
                font-size: 14px;
            }
            h6 {
                font-size: 13px;
            }
            hr {
                margin: 0 0 19px;
                border: 0;
                border-bottom: 1px solid #ccc;
            }
            blockquote {
                padding: 13px 13px 21px 15px;
                margin-bottom: 18px;
                font-family:georgia,serif;
                font-style: italic;
            }
            blockquote:before {
                content:"\201C";
                font-size:40px;
                margin-left:-10px;
                font-family:georgia,serif;
                color:#eee;
            }
            blockquote p {
                font-size: 14px;
                font-weight: 300;
                line-height: 18px;
                margin-bottom: 0;
                font-style: italic;
            }
            code, pre {
                font-family: Monaco, Andale Mono, Courier New, monospace;
            }
            code {
                background-color: #fee9cc;
                color: rgba(0, 0, 0, 0.75);
                padding: 1px 3px;
                font-size: 12px;
                -webkit-border-radius: 3px;
                -moz-border-radius: 3px;
                border-radius: 3px;
            }
            pre {
                display: block;
                padding: 14px;
                margin: 0 0 18px;
                line-height: 16px;
                font-size: 11px;
                border: 1px solid #d9d9d9;
                white-space: pre-wrap;
                word-wrap: break-word;
            }
            pre code {
                background-color: #fff;
                color:#737373;
                font-size: 11px;
                padding: 0;
            }
            sup {
                font-size: 0.83em;
                vertical-align: super;
                line-height: 0;
            }
            * {
                -webkit-print-color-adjust: exact;
            }
            @media screen and (min-width: 914px) {
                body {
                    width: 854px;
                    margin:10px auto;
                }
            }
            @media print {
                body,code,pre code,h1,h2,h3,h4,h5,h6 {
                    color: black;
                }
                table, pre {
                    page-break-inside: avoid;
                }
            }
            iframe{
                width: 100%;
                border: 1px solid #34495E;
                margin: 0;
            }
            .logo{
                vertical-align: middle;
            }

        </style>
        <script>
            function init() {
                var logoSrc = '',
                    logos = document.querySelectorAll('.logo'),
                    i = 0;
                for (; i < logos.length; i++) {
                    logos[i].src = logoSrc;
                }

                var iframes = document.querySelectorAll('iframe'),
                    func = function(){};
                for (i=0; i < iframes.length; i++) {
                    var iframe = iframes[i];

                    // a small hack to make it work on android
                    iframe.ontouchstart = func;

                    var div = document.createElement('div');
                    div.innerHTML = '<a href="' + iframe.src + '" target="_blank">独立运行→</a>';
                    iframe.parentNode.insertBefore(div, iframe);
                }
            }
        </script>
    </head>
    <body onload="init();">
        <a href="http://www.hightopo.com"><img class="logo"></a>HT for Web HtmlNode 手册
        <hr style="margin: 1px 0 20px 0">
<p>索引</p>

<ul><li><a href="#ref_htmlnode">HtmlNode说明</a><ul><li><a href="#ref_sample">简单示例</a></li><li><a href="#ref_binding">数据绑定</a></li><li><a href="#ref_htmltype">支持的HTML类型</a></li><li><a href="#ref_practice">综合应用</a></li></ul></li></ul>

<hr/>

<div id="ref_htmlnode"></div>

<h2>HtmlNode 说明</h2>

<p><code>ht.Node</code>目前支持用图片、矢量来渲染，但是对于某些应用（比如<a href="http://en.wikipedia.org/wiki/SCADA">SCADA</a>)，经常需要在界面中嵌入一些可交互元素(文本框，按钮等)，
此时普通<code>Node</code>就有些力不从心了。为此<code>HT</code>扩展实现了<code>ht.HtmlNode</code>，它可以将<code>HTML</code>元素渲染到拓扑上，既保持<code>HTML</code>元素的可交互性，
又能实现普通<code>Node</code>的缩放旋转等功能。</p>

<p><code>HtmlNode</code>提供了如下<code>API</code>：</p>

<ul><li><code>html</code>通过<code>setHtml</code>和<code>getHtml</code>操作，表示要渲染的<code>HTML</code>内容，可以是<code>HTML</code>文本字符串、<code>DOM</code>对象或<code>HT</code>组件（如<code>GraphView</code>）</li><li><code>context</code>通过<code>setContext</code>和<code>getContext</code>操作，用于<a href="#ref_binding">数据绑定</a></li><li><code>padding</code>通过<code>setPadding</code>和<code>getPadding</code>操作，表示<code>HTML</code>内容边框和<code>Node</code>选中边框之间的距离，默认为<code>6</code></li><li><code>scalable</code>通过<code>setScalable</code>和<code>getScalable</code>操作，表示<code>HTML</code>内容是否可拉伸。值为<code>boolean</code>型(默认为<code>true</code>)：<ul><li><code>true</code>表示当调整<code>HtmlNode</code>的宽高时，用<code>transform</code>样式调整<code>HTML</code>内容的缩放</li><li><code>false</code>表示当调整<code>HtmlNode</code>的宽高时，改变<code>HTML</code>内容的第一个元素的<code>with</code>和<code>height</code>使其适应<code>HtmlNode</code>的尺寸。这意味着：如果此参数设为<code>false</code>，用户设置的
<code>HTML</code>内容应该只有一个根元素</li></ul></li><li><code>setDraggerImage</code>、<code>getDraggerImage</code>用于设置或获取<code>HtmlNode</code>右上角的拖拽图标，必须是通过<code>ht.Default.setImage</code>注册的图片的名字，默认为<code>node_dragger</code></li><li><code>setDraggerImageWidth</code>、<code>getDraggerImageWidth</code>用于设置或获取<code>HtmlNode</code>右上角拖拽图标的宽度</li><li><code>setDraggerImageHeight</code>、<code>getDraggerImageHeight</code>用于设置或获取<code>HtmlNode</code>右上角拖拽图标的高度</li></ul>

<p><code>GraphView</code>上扩展的<code>API</code>：</p>

<ul><li><code>adjustHtmlNodeIndex</code>，默认为<code>true</code>，表示自动调整<code>HTML</code>内容的<code>z-index</code>，让其在选中时置顶显示;设为<code>false</code>可提高性能</li></ul>

<div id="ref_sample"></div>

<h3>简单示例</h3>

<p><code>HtmlNode</code>的<code>html</code>属性可以设置为<code>HTML</code>文本，然后可以按照操作一般<code>Node</code>的方式设置<code>position</code>、<code>width</code>、<code>height</code>等属性。</p>

<p>示例：</p>

<p><iframe src="examples/example_sample.html" style="height:200px"></iframe></p>

<pre><code>var node = new ht.HtmlNode();
node.setPosition(200, 100);
node.setHtml(&quot;&lt;div class=&#39;wrapper&#39;&gt;\n\
    &lt;img width=&#39;20&#39; height=&#39;24&#39; src=&#39;data:image/png;base64,&#39;&gt;\n\
    &amp;nbsp;&amp;nbsp;&amp;nbsp; Hello, I can &lt;i&gt;render&lt;/i&gt; any HTML!&lt;/div&gt;&quot;);
dataModel.add(node);</code></pre>

<p>上例中<code>HtmlNode</code>将一段<code>HTML</code>文本渲染到拓扑上，<code>HTML</code>元素不但可以响应用户动作，而且可以利用拓扑组件对其进行缩放旋转等操作。需要注意：
为了使<code>HTML</code>元素响应用户动作，<code>HtmlNode</code>屏蔽了在<code>body</code>上的拖拽移动功能，当<code>HtmlNode</code>被选中时，其右上角会出现一个十字方向标记，
拖拽它即可实现拖拽移动<code>HtmlNode</code>。</p>

<div id="ref_binding"></div>

<h3>数据绑定</h3>

<p>怎样修改已经展现出来的<code>HTML</code>内容？最直接的方式是给<code>HTML</code>元素指定<code>id</code>，通过<code>document.getElementById(&#39;id&#39;)</code>获取到<code>DOM</code>对象，然后对其进行操作。
除此之外，<code>HtmlNode</code>还提供了简单的双向数据绑定：</p>

<ul><li><p>将数据从<code>HtmlNode</code>的属性更新至<code>HTML</code>元素:</p><p><code>HtmlNode</code>集成<a href="http://handlebarsjs.com/">Handlebars框架</a>以实现将<code>HtmlNode</code>的属性更新至<code>HTML</code>元素。<code>Handlebars</code>可以在运行时将模板中的关键字替换为属性值，<code>Handlebars</code>的详细介绍参考这里：<a href="http://handlebarsjs.com/">http://handlebarsjs.com/</a></p></li><li><p>将<code>HTML</code>元素中的值更新至<code>HtmlNode</code>属性：</p><p><code>HtmlNode</code>在<code>HTML</code>元素的根结点监听<code>change</code>事件，并根据事件目标的<code>bind</code>属性将其<code>value</code>反填回<code>HtmlNode</code>的<code>context</code>属性上，
如下面示例：</p></li></ul>

<p>示例：</p>

<p><iframe src="examples/example_binding.html" style="height:200px"></iframe></p>

<pre><code>&lt;!--引入handlebars--&gt;
&lt;script src=&quot;../../../lib/handlebars.js&quot;&gt;&lt;/script&gt;

&lt;!--创建HTML模板，{{}}可以将HtmlNode上的属性同步到HTML元素中，而HTML元素的bind属性用于将value同步至HtmlNode--&gt;
&lt;script id=&quot;controlpanel-template&quot; type=&quot;text/x-handlebars-template&quot;&gt;
&lt;div class=&quot;panel&quot;&gt;
    &lt;div class=&quot;title&quot;&gt;Dashboard&lt;/div&gt;
    &lt;div class=&quot;content&quot;&gt;
        &lt;p&gt;&lt;span class=&quot;label&quot;&gt;KPI：&lt;/span&gt;&lt;span class=&quot;value&quot;&gt;{{value}}&lt;/span&gt;&lt;/p&gt;
        &lt;p&gt;
        &lt;span class=&quot;label&quot;&gt;Stop：&lt;/span&gt;
        &lt;span class=&quot;value&quot;&gt;&lt;input type=&quot;checkbox&quot; {{#if isStopped}}checked{{/if}} bind=&quot;isStopped&quot;&gt;&lt;/span&gt;
        &lt;/p&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;/script&gt;

......    

var node = new ht.HtmlNode();
node.setPosition(200, 100);
node.setHtml(document.getElementById(&quot;controlpanel-template&quot;).innerHTML);
node.setContext(context);
dataModel.add(node);

/*
* 每隔一秒刷新HtmlNode的context属性，HtmlNode自动将其同步到HTML元素
*/
setInterval(function() {
    if(!context.isStopped){
        context.value=parseInt(100*Math.random());
        graphView.invalidateData(node);
    }
}, 1000);</code></pre>

<div id="ref_htmltype"></div>

<h3>支持的HTML类型</h3>

<p><code>setHtml</code>的参数可以是以下三种类型：</p>

<ul><li><code>HTML</code>文本字符串，如<code>setHtml(&quot;&lt;div&gt;html text&lt;/div&gt;&quot;)</code></li><li><code>DOM</code>对象，开发人员可以通过<code>DOM API</code>创建<code>HTML</code>元素，然后通过<code>setHtml</code>交给<code>HtmlNode</code>渲染</li><li><code>HT</code>对象，<code>HtmlNode</code>也可以渲染<code>GraphView</code>,<code>TablePane</code>等<code>HT</code>组件，需要注意：如果渲染<code>HT</code>组件，最好将<code>scalable</code>
设为<code>false</code></li></ul>

<p>示例：</p>

<p><iframe src="examples/example_htmltype.html" style="height:200px"></iframe></p>

<pre><code>var htmlNode = new ht.HtmlNode();
htmlNode.setPosition(200, 50);
htmlNode.setName(&quot;Rendering HTML text&quot;);
htmlNode.setHtml(&quot;&lt;div class=&#39;htmlWrapper&#39;&gt;Node&#39;s new name：&lt;input type=&#39;text&#39; value=&#39;{{value}}&#39; bind=&#39;value&#39;&gt;\n\
                  &lt;input type=&#39;button&#39; value=&#39;Modify&#39; nodeid=&#39;{{nodeid}}&#39; onclick=&#39;modifyNodeName(event)&#39;/&gt;&lt;/div&gt;&quot;);
htmlNode.setContext({
    value:htmlNode.getName(),
    nodeid:htmlNode.getId()
});
dataModel.add(htmlNode);

var div=document.createElement(&quot;div&quot;);
div.className=&quot;domWrapper&quot;;
div.innerHTML=&quot;Rendering DOM&quot;;
var domNode = new ht.HtmlNode();
domNode.setHtml(div);
domNode.setPosition(200, 150);
dataModel.add(domNode);

var htNode = new ht.HtmlNode();
htNode.setHtml(createTable());
htNode.setPosition(500,100);
htNode.setName(&quot;Alarm Statistics(TablePane)&quot;);
htNode.setScalable(false);
dataModel.add(htNode);</code></pre>

<div id="ref_practice"></div>

<h3>综合应用</h3>

<p>接下来看一个综合应用，有些应用需要在图元的周围显示一些附属信息，比如状态图标、统计表或<code>Chart</code>等。接下来的示例中有两个<code>Node</code>，
一个作为实际展示的<code>Node</code>，另一个作为统计表跟随在第一个<code>Node</code>右侧。为了节省空间，统计表应该可以展开合并，看下面的示例：</p>

<p><iframe src="examples/example_practice.html" style="height:300px"></iframe></p>

<p>首先定义两个模板，作为<code>HtmlNode</code>合并和展开时的<code>HTML</code>内容：</p>

<pre><code>&lt;script id=&quot;table-template&quot; type=&quot;text/x-handlebars-template&quot;&gt;
    &lt;div class=&quot;wrapper&quot; nodeid=&quot;{{id}}&quot; onclick=&quot;tableIconClickHandler(event)&quot;&gt;
    &lt;div class=&quot;table_title&quot;&gt;Real-time monitoring&lt;span class=&quot;shrink&quot;&gt;&lt;/span&gt;&lt;/div&gt;
    &lt;table class=&quot;list_table&quot;&gt;
    {{#each rows}}
        &lt;tr&gt;
            &lt;td&gt;{{id}}&lt;/td&gt;
            &lt;td&gt;{{temperature}}&lt;/td&gt;
            &lt;td&gt;{{pressure}}&lt;/td&gt;
            &lt;td&gt;{{time}}&lt;/td&gt;
        &lt;/tr&gt;
    {{/each}}
    &lt;/table&gt;
    &lt;/div&gt;
&lt;/script&gt;

&lt;script id=&quot;table-min-template&quot; type=&quot;text/x-handlebars-template&quot;&gt;
    &lt;span class=&quot;open&quot; nodeid=&quot;{{id}}&quot; onclick=&quot;tableIconClickHandler(event)&quot;&gt;&lt;/span&gt;
&lt;/script&gt;</code></pre>

<p>接下来声明<code>Host</code>节点和统计表节点：</p>

<pre><code>var host = new ht.Node();
host.setName(&quot;This is Host&quot;);
dataModel.add(host);


var tableNode = new ht.HtmlNode();
tableNode.setHtml(document.getElementById(&quot;table-template&quot;).innerHTML);
tableNode.setPosition(230, 100);
tableNode.setContext({
    id: tableNode.getId(),
    rows: [
        {
            id: 1,
            temperature: 23.3,
            pressure: 231.6,
            time: &quot;18:12&quot;
        },
        {
            id: 2,
            temperature: 29.3,
            pressure: 231,
            time: &quot;18:13&quot;
        },
        {
            id: 3,
            temperature: 25,
            pressure: 211.6,
            time: &quot;18:14&quot;
        },
        {
            id: 4,
            temperature: 22.4,
            pressure: 111.6,
            time: &quot;18:15&quot;
        },
        {
            id: 5,
            temperature: 37.4,
            pressure: 171.6,
            time: &quot;18:19&quot;
        }
    ]
});
tableNode.setWidth(300);
tableNode.setHeight(200);
dataModel.add(tableNode);
tableNode.setPosition({x: host.getPosition().x + host.getWidth() / 2 + tableNode.getWidth() / 2 + 10, 
                       y: host.getPosition().y - host.getHeight() / 2 + tableNode.getHeight() / 2});</code></pre>

<p> 最后还有一个函数处理用户在统计表右上角的点击事件：</p>

<pre><code>function tableIconClickHandler(e) {
    var maxInnerHTML = document.getElementById(&quot;table-template&quot;).innerHTML,
            minInnerHTML = document.getElementById(&quot;table-min-template&quot;).innerHTML;

    if (e.target.className === &quot;shrink&quot;) {
        var nodeid = e.currentTarget.getAttribute(&quot;nodeid&quot;),
                node = dataModel.getDataById(nodeid),
                host = node.getHost(),
                newWidth = 16 + node._padding,
                newHeight = 16 + node._padding,
                originWidth = node._originWidth,
                originHeight = node._originHeight,
                differenceWidth = originWidth - newWidth,
                differenceHeight = originHeight - newHeight;

        ht.Default.startAnim({
            finishFunc: function() {//动画执行结束时调用，改变统计表的内容和位置
                node.setHtml(minInnerHTML);
                node.setPosition({x: host.getPosition().x + host.getWidth() / 2 + node.getWidth() / 2 + 10, 
                                  y: host.getPosition().y - host.getHeight() / 2 + node.getHeight() / 2});
            },
            action: function(t) {//动画每前进一帧就都会调用此方法，在这里调整统计表的尺寸的位置
                node.setWidth(originWidth - differenceWidth * t);
                node.setHeight(originHeight - differenceHeight * t);
                node.setPosition({x: host.getPosition().x + host.getWidth() / 2 + node.getWidth() / 2 + 10, 
                                  y: host.getPosition().y - host.getHeight() / 2 + node.getHeight() / 2});
            }
        });
    } else if (e.target.className === &quot;expand&quot;) {
        var nodeid = e.currentTarget.getAttribute(&quot;nodeid&quot;),
                node = dataModel.getDataById(nodeid),
                host = node.getHost(),
                originWidth = node._originWidth,
                originHeight = node._originHeight;

        node.setHtml(maxInnerHTML);

        var newWidth = node.getWidth(),
                newHeight = node.getHeight(),
                differenceWidth = newWidth - originWidth,
                differenceHeight = newHeight - originHeight;

        node.setWidth(originWidth);
        node.setHeight(originHeight);

        ht.Default.startAnim({
            action: function(t) {
                node.setWidth(originWidth + differenceWidth * t);
                node.setHeight(originHeight + differenceHeight * t);
                node.setPosition({x: host.getPosition().x + host.getWidth() / 2 + node.getWidth() / 2 + 10,
                                  y: host.getPosition().y - host.getHeight() / 2 + node.getHeight() / 2});
            }
        });
    }
}</code></pre>    <hr id="contact" style="margin: 20px 0 1px 0">
    <a href="http://www.hightopo.com"><img class="logo"></a>欢迎交流 <a href="mailto:service@hightopo.com">service@hightopo.com</a>
</body>
</html>
